﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Bovril.PlayingCards.French;

namespace Bovril.Poker
{
    public enum HandCategory
    {
        StraightFlush,
        FourOfAKind,
        FullHouse,
        Flush,
        Straight,
        ThreeOfAKind,
        TwoPairs,
        OnePair,
        Nowt// as me fatha would say
    }

    internal class HandGrouping
    {
        internal HandGrouping(int rank, int count)
        {
            Rank = rank;
            Count = count;
        }

        internal int Rank { get; private set; }
        internal int Count { get; private set; }
    }

    public class Hand : IComparable<Hand>
    {
        public Hand(Card card1, Card card2, Card card3, Card card4, Card card5)
        {
            cards_ = new Card[5]
            {
                card1, card2, card3, card4, card5
            };

#if DEBUG
            for (int x = 0; x < 4; ++x)
            {
                for (int y = x + 1; y < 5; ++y)
                {
                    System.Diagnostics.Debug.Assert(!cards_[x].Equals(cards_[y]));
                }
            }
#endif

            Category = GetCategory();
        }

        public override string ToString()
        {
            return String.Format("{0}, {1}, {2}, {3}, {4}",
                Card1.ToString(), Card2.ToString(), Card3.ToString(), Card4.ToString(), Card5.ToString());
        }

        #region IComparable<Hand> Interface
        public int CompareTo(Hand other)
        {
            if (this.Category < other.Category)
                return 1;
            else if (this.Category > other.Category)
                return -1;

            for (int i = 0; i < 5; ++i)
            {
                if (this.cards_[i].Rank > other.cards_[i].Rank)
                    return 1;
                else if (this.cards_[i].Rank < other.cards_[i].Rank)
                    return -1;
            }

            return 0;
        }
        #endregion IComparable<Hand> Interface

        private int GetGroupings(out HandGrouping majorGrouping, out HandGrouping minorGrouping)
        {
            for (int i = RankValue.MinValue; i <= RankValue.MaxValue; ++i)
                groupingData_[i] = 0;

            foreach (Card card in cards_)
                ++groupingData_[(int)card.Rank];

            int groupingRank0 = RankValue.Two;
            int groupingCount0 = -1;
            int groupingRank1 = RankValue.Two;
            int groupingCount1 = -1;
            for (int i = RankValue.MinValue; i <= RankValue.MaxValue; ++i)
            {
                if (groupingData_[i] >= 2)
                {
                    if (groupingCount0 == -1)
                    {
                        groupingRank0 = i;
                        groupingCount0 = groupingData_[i];
                    }
                    else
                    {
                        groupingRank1 = i;
                        groupingCount1 = groupingData_[i];
                    }
                }
            }

            if(groupingCount0 == -1)
            {
                majorGrouping = null;
                minorGrouping = null;
                return 0;
            }

            if(groupingCount1 == -1)
            {
                majorGrouping = new HandGrouping(groupingRank0, groupingCount0);
                minorGrouping = null;
                return 1;
            }

            bool group0IsMajor = (groupingCount0 > groupingCount1) ||
                                 (groupingCount0 == groupingCount1 && (groupingRank0 > (groupingRank1)));

            if (group0IsMajor)
            {
                majorGrouping = new HandGrouping(groupingRank0, groupingCount0);
                minorGrouping = new HandGrouping(groupingRank1, groupingCount1);
            }
            else
            {
                majorGrouping = new HandGrouping(groupingRank1, groupingCount1);
                minorGrouping = new HandGrouping(groupingRank0, groupingCount0);
            }

            return 2;
        }

        private HandCategory GetCategory()
        {
            HandGrouping majorGrouping;
            HandGrouping minorGrouping;
            int numGroupings = GetGroupings(out majorGrouping, out minorGrouping);

            if (numGroupings == 1 && majorGrouping.Count == 4)
            {
                Array.Sort(cards_, new CardComparer(majorGrouping.Rank));
                return HandCategory.FourOfAKind;
            }

            if (numGroupings == 2 && majorGrouping.Count == 3)
            {
                Array.Sort(cards_, new CardComparer(majorGrouping.Rank));
                return HandCategory.FullHouse;
            }

            if (numGroupings == 1 && majorGrouping.Count == 3)
            {
                Array.Sort(cards_, new CardComparer(majorGrouping.Rank));
                return HandCategory.ThreeOfAKind;
            }

            if (numGroupings == 2 && majorGrouping.Count == 2)
            {
                Array.Sort(cards_, new CardComparer(majorGrouping.Rank, minorGrouping.Rank));
                return HandCategory.TwoPairs;
            }

            if (numGroupings == 1 && majorGrouping.Count == 2)
            {
                Array.Sort(cards_, new CardComparer(majorGrouping.Rank));
                return HandCategory.OnePair;
            }

            Array.Sort(cards_, new CardComparer());

            bool isFlush = IsFlush();
            bool isStraight = IsStraight();

            if (isFlush && isStraight)
                return HandCategory.StraightFlush;

            if (isFlush)
                return HandCategory.Flush;

            if (isStraight)
                return HandCategory.Straight;

            return HandCategory.Nowt;
        }

        private bool IsFlush()
        {
            return ((cards_[0].Suit == cards_[1].Suit) &&
                    (cards_[0].Suit == cards_[2].Suit) &&
                    (cards_[0].Suit == cards_[3].Suit) &&
                    (cards_[0].Suit == cards_[4].Suit));
        }

        private bool IsStraight()// need to cater for A-2-3-4-5 as well as A-K-Q-J-10
        {
            if (cards_[0].Rank != RankValue.Ace)
            {
                return (((cards_[0].Rank - cards_[1].Rank) == 1) &&
                        ((cards_[1].Rank - cards_[2].Rank) == 1) &&
                        ((cards_[2].Rank - cards_[3].Rank) == 1) &&
                        ((cards_[3].Rank - cards_[4].Rank) == 1));
            }
            else
            {
                return ((cards_[4].Rank == RankValue.Two && cards_[3].Rank == RankValue.Three && cards_[2].Rank == RankValue.Four && cards_[1].Rank == RankValue.Five) ||
                        (cards_[1].Rank == RankValue.King && cards_[2].Rank == RankValue.Queen && cards_[3].Rank == RankValue.Jack && cards_[4].Rank == RankValue.Ten));
            }
        }

        private void SwapCards(int x, int y)
        {
            Card temp = cards_[x];
            cards_[x] = cards_[y];
            cards_[y] = temp;
        }

        public HandCategory Category { get; private set; }

        public Card Card1 { get { return cards_[0]; } }
        public Card Card2 { get { return cards_[1]; } }
        public Card Card3 { get { return cards_[2]; } }
        public Card Card4 { get { return cards_[3]; } }
        public Card Card5 { get { return cards_[4]; } }

        private static int[] groupingData_ = new int[15];

        private Card[] cards_;
    }
}
